package com.gitee.wsl.struct.grammar

import assertk.assertThat
import assertk.assertions.isEqualTo
import com.gitee.wsl.struct.grammar.JsonSamples.jsonSample1K
import com.gitee.wsl.struct.grammar.parser.Parser
import com.gitee.wsl.struct.grammar.parser.combinator.and
import com.gitee.wsl.struct.grammar.parser.result.getOrThrow
import com.gitee.wsl.struct.grammar.parser.combinator.map
import com.gitee.wsl.struct.grammar.parser.combinator.or
import com.gitee.wsl.struct.grammar.parser.combinator.skip
import com.gitee.wsl.struct.grammar.parser.combinator.times
import com.gitee.wsl.struct.grammar.parser.combinator.unaryMinus
import com.gitee.wsl.struct.grammar.parser.token.Token
import com.gitee.wsl.struct.grammar.parser.token.literalToken
import com.gitee.wsl.struct.grammar.parser.token.num
import com.gitee.wsl.struct.grammar.parser.token.regexToken
import com.gitee.wsl.struct.grammar.parser.token.string
import com.gitee.wsl.struct.grammar.parser.token.token
import com.gitee.wsl.struct.grammar.ast.node.ASTNode
import com.gitee.wsl.struct.grammar.ast.node.sample.lexeme
import com.gitee.wsl.struct.grammar.ext.parser
import kotlin.test.Test

class GrammarTests {

    @Test
    fun singleTokenGrammar() {
        object : Grammar<ASTNode>() {
            val a by literalToken("a")
            override val root = parser { lexeme(a) }
        }.let { g ->
            assertThat(g.parseOrThrow("a")).isEqualTo(
                g.a.lex(0)
            )
        }
    }

    @Test
    fun emptyGrammar() {
        object : Grammar<Unit>() {
            override val root = parser {}
        }.let { g ->
            assertThat(g.parseOrThrow("")).isEqualTo(Unit)
        }

        object : Grammar<ASTNode>() {
            val a by literalToken("a")
            val empty = parser {}
            override val root = parser { skip(empty) * lexeme(a) }
        }.let { g ->
            assertThat(g.parseOrThrow("a")).isEqualTo(g.a.lex(0))
        }
    }

    @Test
    fun recursiveGrammar() {
        object : Grammar<ASTNode>() {
            val lp by literalToken("(")
            val rp by literalToken(")")
            val a by literalToken("a")
            val ap by parser { lexeme(a) }
            val p: Parser<ASTNode> by ap or parser { skip(lp) * p() * skip(rp) }
            override val root = p
        }.run {
            assertParsed("a").isEqualTo(a.lex(0))
            assertParsed("((a))").isEqualTo(a.lex(2))
        }

        object : Grammar<ASTNode>() {
            val lp by literalToken("(")
            val rp by literalToken(")")
            val a by literalToken("a")
            val ap by parser { lexeme(a) }
            val p: Parser<ASTNode> by ap or (-lp * ref(::p) * -rp)
            override val root = p
        }.run {
            assertParsed("a").isEqualTo(a.lex(0))
            assertParsed("((a))").isEqualTo(a.lex(2))
        }
    }

    @Test
    fun parseWithNonRootParser() {
        object : Grammar<ASTNode>() {
            val a by literalToken("a")
            val b by literalToken("b")
            val nonRootParser by parser { lexeme(b) }
            override val root = parser { lexeme(a) }
        }.run {
            assertParsed("a").isEqualTo(a.lex())
            assertThatParsing("b").failedWithUnmatchedToken(a, 0)
            assertThat(parse(nonRootParser, "b").getOrThrow()).isEqualTo(b.lex())
        }
    }

    @Test
    fun naiveJsonGrammarTest() {
        NaiveJsonGrammar.run {
            assertParsed("""{${'\n'}"a": 1,${'\n'}"b": {"c":false}${'\n'}}""").isEqualTo(
                Json.Obj(
                    mapOf(
                        "a" to Json.Num(1.0),
                        "b" to Json.Obj(mapOf("c" to Json.Bool(false)))
                    )
                )
            )
        }
    }

    @Test
    fun testParserJson(){
        println(FasterJsonGrammar.parseOrThrow(jsonSample1K).jsonToString())
    }


}

object NaiveJsonGrammar : Grammar<Json>() {
    init {
        regexToken("\\s+", ignored = true)
    }

    private val comma by literalToken(",")
    private val colon by literalToken(":")
    private val lbrace by literalToken("{")
    private val rbrace by literalToken("}")
    private val lbracket by literalToken("[")
    private val rbracket by literalToken("]")
    private val str by regexToken("\"[^\\\\\"]*(\\\\[\"nrtbf\\\\][^\\\\\"]*)*\"") map { it.text.run { substring(1, lastIndex) } }
    private val jsonTrue by literalToken("true") map { Json.Bool(true) }
    private val jsonFalse by literalToken("false") map { Json.Bool(false) }
    private val jsonNull by literalToken("null") map Json.Null
    private val jsonNum by regexToken("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?") map { Json.Num(it.text.toDouble()) }
    private val jsonStr by str map { Json.Str(it) }

    private val keyValue by str * -colon and ref(::jsonValue) map { it }
    private val jsonObj by -lbrace * separated(keyValue, comma) * -rbrace map { Json.Obj(it.toMap()) }

    private val jsonArr by -lbracket * separated(ref(::jsonValue), comma) * -rbracket map { Json.Arr(it) }
    private val jsonValue: Parser<Json> by jsonNull or jsonTrue or jsonFalse or jsonNum or jsonStr or jsonArr or jsonObj
    override val root by jsonValue
}

object FasterJsonGrammar : Grammar<Json>() {

    init {
        // Ignored whitespace
        token(ignored = true, firstChars = " \n\t") { it, at ->
            var index = at
            val length = it.length
            while (index < length && it[index].isWhitespace()) {
                index++
            }
            index - at
        }
    }

    private val comma by literalToken(",")
    private val colon by literalToken(":")
    private val lbrace by literalToken("{")
    private val rbrace by literalToken("}")
    private val lbracket by literalToken("[")
    private val rbracket by literalToken("]")
    private val nullToken by literalToken("null")
    private val trueToken by literalToken("true")
    private val falseToken by literalToken("false")

    private val numToken by Token.num.registed()

    private val stringToken by Token.string.registed()

    private val str by stringToken map { it.text.run { substring(1, lastIndex) } }
    private val jsonNull by nullToken map Json.Null
    private val jsonBool by trueToken or falseToken map { Json.Bool(it.token == trueToken) }
    private val jsonNum by numToken map { Json.Num(it.text.toDouble()) }
    private val jsonStr by str map { Json.Str(it) }

    private val keyValue by str and -colon and ref(::jsonValue) map { it }
    private val jsonObj by -lbrace * separated(keyValue, comma) * -rbrace map { Json.Obj(it.toMap()) }

    private val jsonArr by -lbracket * separated(ref(::jsonValue), comma) * -rbracket map { Json.Arr(it) }
    private val jsonValue: Parser<Json> by jsonNull or jsonBool or jsonNum or jsonStr or jsonArr or jsonObj

    override val root by jsonValue
}